﻿using System;
using System.Diagnostics;
using System.Reflection;
using JetBrains.Annotations;

namespace ExceptionModule
{
    public static class StacktraceHelper
    {
        /// <summary>
        /// 获得抛出异常的函数位置
        /// </summary>
        /// <param name="e">异常</param>
        /// <returns>返回抛出异常的函数</returns>
        public static MethodBase GetExceptionTargetSite(Exception e)
        {
            var capturedTraces = GetCapturedTraces(e);
            if (capturedTraces == null)
            {
                if (e.TargetSite == null)
                    return GetLogTargetSite();
                return e.TargetSite;
            }

            var stackFrame = capturedTraces.GetFrames()[0];
            return stackFrame.GetMethod();
        }

        /// <summary>
        /// 获取调用Debug.Log等函数的调用函数的位置
        /// </summary>
        /// <returns>返回调用Debug.Log等函数的调用函数的位置</returns>
        public static MethodBase GetLogTargetSite()
        {
            var stackTrace = new StackTrace();
            var frames = stackTrace.GetFrames();
            return GetCallLogFunction(frames);
        }

        private static StackTrace GetCapturedTraces(Exception e)
        {
            var exceptionType = e.GetType();
            var field = exceptionType.GetField("captured_traces", BindingFlags.NonPublic | BindingFlags.Instance);
            var capturedTraces = (StackTrace[])field.GetValue(e);
            if (capturedTraces == null)
                return null;
            
            foreach (var capturedTrace in capturedTraces)
            {
                if (capturedTrace.GetFrames().Length > 0)
                    return capturedTrace;
            }
            return null;
        }
        
        private static MethodBase GetCallLogFunction([NotNull] StackFrame[] frames)
        {
            if (frames == null) throw new ArgumentNullException(nameof(frames));
            for (var i = 0; i < frames.Length; i++)
            {
                var frame = frames[i];
                var method = frame.GetMethod();
                if (IsUnityDebugLog(method))
                {
                    var nextFrameIndex = i + 1;
                    return frames[nextFrameIndex].GetMethod();
                }
            }

            throw new Exception("没找到调用Debug.Log或者Debug.LogException的函数");
        }
        
        private static bool IsUnityDebugLog(MethodBase method)
        {
            return method.ReflectedType.FullName == "UnityEngine.Debug" &&
                   (method.Name == "Log" ||
                    method.Name == "LogFormat" ||
                    method.Name == "LogAssertion" ||
                    method.Name == "LogAssertionFormat" ||
                    method.Name == "LogWarning" ||
                    method.Name == "LogWarningFormat" ||
                    method.Name == "LogError" ||
                    method.Name == "LogErrorFormat" ||
                    method.Name == "LogException");
        }
    }
}